/*  $Revision: 10324 $
**
**  History file routines.
*/
#include "innbbsconf.h"
#include "bbslib.h"
#include "his.h"
#include "inn_funcs.h"

#define STATIC static
/*STATIC char	HIShistpath[] = _PATH_HISTORY;*/
STATIC FILE *HISwritefp;
STATIC int HISreadfd;
STATIC int HISdirty;
STATIC int HISincore = XINDEX_DBZINCORE;
STATIC char *LogName = "xindexchan";

#ifndef EXPIREDAYS
#define EXPIREDAYS 4
#endif

#ifndef DEFAULT_HIST_SIZE
#define DEFAULT_HIST_SIZE 100000
#endif

void hisincore(int flag)
{
    HISincore = flag;
}

void makedbz(char *histpath, long entry)
{
    long size;

    size = dbzsize(entry);
    dbzfresh(histpath, size, '\t', 0, 1);
    dbmclose();
}

void myHISsetup(char *histpath);
void HISsetup();
void HISclose();
BOOL myHISwrite(datum *key, char *remain);

void mkhistory(char *srchist)
{
    FILE *hismaint;
    char maintbuff[256];
    char *ptr;

    hismaint = fopen(srchist, "r");
    if (hismaint == NULL) {
        return;
    }
    {
        char newhistpath[1024];
        char newhistdirpath[1024];
        char newhistpagpath[1024];

        sprintf(newhistpath, "%s.n", srchist);
        sprintf(newhistdirpath, "%s.n.dir", srchist);
        sprintf(newhistpagpath, "%s.n.pag", srchist);
        if (!isfile(newhistdirpath) || !isfile(newhistpagpath)) {
            makedbz(newhistpath, DEFAULT_HIST_SIZE);
        }
        myHISsetup(newhistpath);
        while (fgets(maintbuff, sizeof(maintbuff), hismaint) != NULL) {
            datum key;

            ptr = (char *) strchr(maintbuff, '\t');
            if (ptr != NULL) {
                *ptr = '\0';
                ptr++;
            }
            key.dptr = maintbuff;
            key.dsize = strlen(maintbuff);
            myHISwrite(&key, ptr);
        }
        (void) HISclose();
        /*
         * rename(newhistpath, srchist);
         * rename(newhistdirpath, fileglue("%s.dir", srchist));
         * rename(newhistpagpath, fileglue("%s.pag", srchist));
         */
    }
    fclose(hismaint);
}

time_t gethisinfo()
{
    FILE *hismaint;
    time_t lasthist;
    char maintbuff[4096];
    char *ptr;

    hismaint = fopen(HISTORY, "r");
    if (hismaint == NULL) {
        return 0;
    }
    fgets(maintbuff, sizeof(maintbuff), hismaint);
    fclose(hismaint);
    ptr = (char *) strchr(maintbuff, '\t');
    if (ptr != NULL) {
        ptr++;
        lasthist = atol(ptr);
        return lasthist;
    }
    return 0;
}

void HISmaint()
{
    FILE *hismaint;
    time_t lasthist, now;
    char maintbuff[4096];
    char *ptr;

    if (!isfile(HISTORY)) {
        makedbz(HISTORY, DEFAULT_HIST_SIZE);
    }
    hismaint = fopen(HISTORY, "r");
    if (hismaint == NULL) {
        return;
    }
    fgets(maintbuff, sizeof(maintbuff), hismaint);
    ptr = (char *) strchr(maintbuff, '\t');
    if (ptr != NULL) {
        ptr++;
        lasthist = atol(ptr);
        time(&now);
        if (lasthist + 86400 * Expiredays * 2 < now) {
            char newhistpath[1024];
            char newhistdirpath[1024];
            char newhistpagpath[1024];

            (void) HISclose();
            sprintf(newhistpath, "%s.n", HISTORY);
            sprintf(newhistdirpath, "%s.n.dir", HISTORY);
            sprintf(newhistpagpath, "%s.n.pag", HISTORY);
            if (!isfile(newhistdirpath)) {
                makedbz(newhistpath, DEFAULT_HIST_SIZE);
            }
            myHISsetup(newhistpath);
            while (fgets(maintbuff, sizeof(maintbuff), hismaint) != NULL) {
                datum key;

                ptr = (char *) strchr(maintbuff, '\t');
                if (ptr != NULL) {
                    *ptr = '\0';
                    ptr++;
                    lasthist = atol(ptr);
                } else {
                    continue;
                }
                if (lasthist + 99600 * Expiredays < now)
                    continue;
                key.dptr = maintbuff;
                key.dsize = strlen(maintbuff);
                myHISwrite(&key, ptr);
            }
            (void) HISclose();
            rename(HISTORY, (char *) fileglue("%s.o", HISTORY));
            rename(newhistpath, HISTORY);
            rename(newhistdirpath, (char *) fileglue("%s.dir", HISTORY));
            rename(newhistpagpath, (char *) fileglue("%s.pag", HISTORY));
            (void) HISsetup();
        }
    }
    fclose(hismaint);
}

/*
**  Set up the history files.
*/
void HISsetup()
{
    myHISsetup(HISTORY);
}

void myHISsetup(char *histpath)
{
    if (HISwritefp == NULL) {
        /*
         * Open the history file for appending formatted I/O. 
         */
        if ((HISwritefp = fopen(histpath, "a")) == NULL) {
            syslog(LOG_CRIT, "%s cant fopen %s %m", LogName, histpath);
            exit(1);
        }
        CloseOnExec((int) fileno(HISwritefp), TRUE);

        /*
         * Open the history file for reading. 
         */
        if ((HISreadfd = open(histpath, O_RDONLY)) < 0) {
            syslog(LOG_CRIT, "%s cant open %s %m", LogName, histpath);
            exit(1);
        }
        CloseOnExec(HISreadfd, TRUE);

        /*
         * Open the DBZ file. 
         */
        /*
         * (void)dbzincore(HISincore); 
         */
        (void) dbzincore(HISincore);
        (void) dbzwritethrough(1);
        if (dbminit(histpath) < 0) {
            syslog(LOG_CRIT, "%s cant dbminit %s %m", histpath, LogName);
            exit(1);
        }
    }
}

/*
**  Synchronize the in-core history file (flush it).
*/
void HISsync()
{
    if (HISdirty) {
        if (dbzsync()) {
            syslog(LOG_CRIT, "%s cant dbzsync %m", LogName);
            exit(1);
        }
        HISdirty = 0;
    }
}

/*
**  Close the history files.
*/
void HISclose()
{
    if (HISwritefp != NULL) {
        /*
         * Since dbmclose calls dbzsync we could replace this line
         * with "HISdirty = 0;".  Oh well, it keeps the abstraction
         * clean.
         */
        HISsync();
        if (dbmclose() < 0)
            syslog(LOG_ERR, "%s cant dbmclose %m", LogName);
        if (fclose(HISwritefp) == EOF)
            syslog(LOG_ERR, "%s cant fclose history %m", LogName);
        HISwritefp = NULL;
        if (close(HISreadfd) < 0)
            syslog(LOG_ERR, "%s cant close history %m", LogName);
        HISreadfd = -1;
    }
}

#ifdef HISset
/*
**  File in the DBZ datum for a Message-ID, making sure not to copy any
**  illegal characters.
*/
STATIC void HISsetkey(p, keyp)
register char *p;
datum *keyp;
{
    static BUFFER MessageID;
    register char *dest;
    register int i;

    /*
     * Get space to hold the ID. 
     */
    i = strlen(p);
    if (MessageID.Data == NULL) {
        MessageID.Data = NEW(char, i + 1);

        MessageID.Size = i;
    } else if (MessageID.Size < i) {
        RENEW(MessageID.Data, char, i + 1);

        MessageID.Size = i;
    }
    for (keyp->dptr = dest = MessageID.Data; *p; p++)
        if (*p == HIS_FIELDSEP || *p == '\n')
            *dest++ = HIS_BADCHAR;
        else
            *dest++ = *p;
    *dest = '\0';

    keyp->dsize = dest - MessageID.Data + 1;
}
#endif
/*
**  Get the list of files under which a Message-ID is stored.
*/
char *HISfilesfor(key, output)
datum *key;
datum *output;
{
    char *dest;
    datum val;
    off_t offset;
    register char *p;
    register int i;
    int Used;

    /*
     * Get the seek value into the history file. 
     */
    val = dbzfetch(*key);
    if (val.dptr == NULL || val.dsize != sizeof offset) {
        /*
         * printf("fail here val.dptr %d\n",val.dptr); 
         */
        return NULL;
    }
    /*
     * Get space. 
     */
    if (output->dptr == NULL) {
        printf("fail here output->dptr null\n");
        return NULL;
    }
    /*
     * Copy the value to an aligned spot. 
     */
    for (p = val.dptr, dest = (char *) &offset, i = sizeof offset; --i >= 0;)
        *dest++ = *p++;
    if (lseek(HISreadfd, offset, SEEK_SET) == -1) {
        printf("fail here lseek %ld\n", offset);
        return NULL;
    }
    /*
     * Read the text until \n or EOF. 
     */
    for (output->dsize = 0, Used = 0;;) {
        i = read(HISreadfd, &output->dptr[output->dsize], LEN - 1);
        if (i <= 0) {
            printf("fail here i %d\n", i);
            return NULL;
        }
        Used += i;
        output->dptr[Used] = '\0';
        if ((p = (char *) strchr(output->dptr, '\n')) != NULL) {
            *p = '\0';
            break;
        }
    }

    /*
     * Move past the first two fields -- Message-ID and date info. 
     */
    if ((p = (char *) strchr(output->dptr, HIS_FIELDSEP)) == NULL) {
        printf("fail here no HIS_FILE\n");
        return NULL;
    }
    return p + 1;
    /*
     * if ((p = (char*)strchr(p + 1, HIS_FIELDSEP)) == NULL) return NULL;
     */

    /*
     * Translate newsgroup separators to slashes, return the fieldstart. 
     */
}

/*
**  Have we already seen an article?
*/
#ifdef HISh
BOOL HIShavearticle(MessageID)
char *MessageID;
{
    datum key;
    datum val;

    HISsetkey(MessageID, &key);
    val = dbzfetch(key);
    return val.dptr != NULL;
}
#endif

#if 0
/*
**  Turn a history filename entry from slashes to dots.  It's a pity
**  we have to do this.
*/
STATIC void HISslashify(p)
register char *p;
{
    register char *last;

    for (last = NULL; *p; p++) {
        if (*p == '/') {
            *p = '.';
            last = p;
        } else if (*p == ' ' && last != NULL)
            *last = '/';
    }
    if (last)
        *last = '/';
}
#endif

void IOError(char *error)
{
    fprintf(stderr, "%s\n", error);
}

BOOL myHISwrite(datum *key, char *remain)
{
    long offset;
    datum val;
    int i;

    val = dbzfetch(*key);
    if (val.dptr != NULL) {
        return FALSE;
    }
    writew_lock(fileno(HISwritefp), 0, SEEK_SET, 0);
    offset = ftell(HISwritefp);
    i = fprintf(HISwritefp, "%s%c%s", key->dptr, HIS_FIELDSEP, remain);
    if (i == EOF || fflush(HISwritefp) == EOF) {
        /*
         * The history line is now an orphan... 
         */
        IOError("history");
        syslog(LOG_ERR, "%s cant write history %m", LogName);
        un_lock(fileno(HISwritefp), 0, SEEK_SET, 0);
        return FALSE;
    }
    /*
     * Set up the database values and write them. 
     */
    val.dptr = (char *) &offset;
    val.dsize = sizeof offset;
    if (dbzstore(*key, val) < 0) {
        IOError("my history database");
        syslog(LOG_ERR, "%s cant dbzstore %m", LogName);
        un_lock(fileno(HISwritefp), 0, SEEK_SET, 0);
        return FALSE;
    }
    if (++HISdirty >= ICD_SYNC_COUNT)
        HISsync();
    un_lock(fileno(HISwritefp), 0, SEEK_SET, 0);
    return TRUE;
}

/*
**  Write a history entry.
*/
BOOL HISwrite(datum *key, long date, const char *paths)
{
    long offset;
    datum val;
    int i;

    writew_lock(fileno(HISwritefp), 0, SEEK_SET, 0);
    offset = ftell(HISwritefp);
    i = fprintf(HISwritefp, "%s%c%ld%c%s\n", key->dptr, HIS_FIELDSEP, (long) date, HIS_FIELDSEP, paths);
    if (i == EOF || fflush(HISwritefp) == EOF) {
        /*
         * The history line is now an orphan... 
         */
        IOError("history");
        syslog(LOG_ERR, "%s cant write history %m", LogName);
        un_lock(fileno(HISwritefp), 0, SEEK_SET, 0);
        return FALSE;
    }
    /*
     * Set up the database values and write them. 
     */
    val.dptr = (char *) &offset;
    val.dsize = sizeof offset;
    if (dbzstore(*key, val) < 0) {
        IOError("history database");
        syslog(LOG_ERR, "%s cant dbzstore %m", LogName);
        un_lock(fileno(HISwritefp), 0, SEEK_SET, 0);
        return FALSE;
    }
    if (++HISdirty >= ICD_SYNC_COUNT)
        HISsync();
    un_lock(fileno(HISwritefp), 0, SEEK_SET, 0);
    return TRUE;
}
